﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using SharedGenomics.Core;

namespace SharedGenomics.Domain
{
    public class AnalysisResultsFileParser
    {
        public IAnalysisResultSet ParseResultsFile(IEnumerable<TextDataRow> resultsFileLines, JobType resultsJobType)
        {
            return this.ParseResultsFile(resultsFileLines, resultsJobType, new DefaultAnalysisResultsFileReadStrategy());
        }

        public virtual IAnalysisResultSet ParseResultsFile(IEnumerable<TextDataRow> resultsFileLines, JobType resultsJobType, IAnalysisResultsFileReadStrategy readStrategy)
        {
            resultsFileLines.ThrowIfNull("resultsFileLines");
            readStrategy.ThrowIfNull("readStrategy");

            switch (resultsJobType)
            {
                case JobType.PassocBT:
                    return ParseResultsFile<AssocAnalysisResult>(resultsFileLines, readStrategy);

                case JobType.PassocQT:
                    return ParseResultsFile<AssocQtAnalysisResult>(resultsFileLines, readStrategy);

                case JobType.PCMH:
                    return ParseResultsFile<CMHAnalysisResult>(resultsFileLines, readStrategy);

                case JobType.PModel:
                    return ParseResultsFile<ModelAnalysisResult>(resultsFileLines, readStrategy);

                case JobType.FastPepiBT:
                    return ParseResultsFile<FastEpistasisAnalysisResult>(resultsFileLines, readStrategy);

                default:
                    throw new ArgumentException("Unknown file type");
            }
        }

        private IAnalysisResultSet ParseResultsFile<T>(IEnumerable<TextDataRow> resultsFileLines, IAnalysisResultsFileReadStrategy readStrategy) where T : AnalysisResult, new()
        {
            AnalysisResultSet<T> results = new AnalysisResultSet<T>();

            //get a list of the data properties on the target type and the associated column names in the data
            var dataProperties = from pi in typeof(T).GetProperties()
                                 let atts = pi.GetCustomAttributes(typeof(AnalysisResultColumnAttribute), true)
                                 where atts.Length > 0
                                 select new { PropertyInfo = pi, ColumnAttribute = (AnalysisResultColumnAttribute)atts[0] };

            //reset read strategy to clear any state from any previous result file loads
            readStrategy.Reset();

            foreach (TextDataRow row in resultsFileLines ?? Enumerable.Empty<TextDataRow>())
            {
                T currentResult = new T();

                foreach (var dp in dataProperties)
                {
                    string columnName = dp.ColumnAttribute.ColumnName ?? dp.PropertyInfo.Name;
                    TypeConverter converter = TypeDescriptor.GetConverter(dp.PropertyInfo.PropertyType);
                    string columnTextValue = row[columnName];

                    try
                    {
                        object convertedValue = converter.ConvertFromString(columnTextValue);
                        dp.PropertyInfo.SetValue(currentResult, convertedValue, null);
                    }
                    catch (Exception)
                    {
                        //parse failed for this property - if the attribute has a default value then use that, otherwise the parse has failed
                        ExceptionHelper.ThrowIf<FileParseException>(!dp.ColumnAttribute.HasDefault, String.Format("Failed to convert value '{0}' for field {1} to type {2} on line {3} and no default is specified - parse fails", columnTextValue, dp.PropertyInfo.Name, dp.PropertyInfo.PropertyType.Name, readStrategy.LinesRead + 1));
                        dp.PropertyInfo.SetValue(currentResult, dp.ColumnAttribute.DefaultOnParseFailed, null);
                    }
                }

                if (readStrategy.ShouldAddAndContinue(currentResult))
                {
                    results.Add(currentResult);
                }
                else
                {
                    break;
                }
            }

            return results;
        }
    }
}
